////////////////////////////////////////
// EECE494 COMPUTER GRAPHICS
// Spring term 2011
// Assignment 1 Model Rendering
// (Tim) Wei-Ting Liu
// #29144052
////////////////////////////////////////

#include <iostream>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <GL/glut.h>
#include "loadModel.h"
#include "PPM.hpp"

using namespace std;

// Print DEBUG info to console
static const bool DEBUG = 0;

string modelName;
string modelPath;

vector<float> vertexVT;
vector<float> normalVT;
vector<string> textureVT;
GLuint* texturePPM;
vector<triangle> triangles;

// Mouse motion
static int rotateBeginX, rotateBeginY;
static int turnX =0, turnY = 0;
// Keyboard direction key
static float angle = 0.0f;
static float lookX = 0.0f, lookY = 0.0f, lookZ = -1.0f;
static float camX = 0.0f, camY = 0.0f, camZ = 0.0f;

// Convert PPM file using PPM class provided
void loadTexture(vector<string> textureVT){
	texturePPM = new GLuint[textureVT.size()];
	
	int textureWidth;
	int textureHeight;

	for(int i=0; i<textureVT.size(); i++){
		unsigned char* tr;
		try{
			tr = PPM::Read(textureVT[i], textureWidth, textureHeight);
		}catch (string str){
			cout << "Exception from PPM.cpp: " << str << endl;
		}
		glGenTextures(1, &texturePPM[i]);
		glBindTexture(GL_TEXTURE_2D, texturePPM[i]);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, textureWidth, textureHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, tr);

		delete tr;
	}
}

// Creates loadModel object, which parses the input data
// Stores model's vectices and normals into arrays
void loadFile(){
	loadModel model(modelPath);

	vertexVT = model.getVertex();
	normalVT = model.getNormal();
	textureVT = model.getTexture();
	loadTexture(textureVT);
	triangles = model.getTriangle();

	if(DEBUG){
		cout << "vectorMax: " << vertexVT.max_size() << endl;
		cout << "Vsize: " << vertexVT.size() << endl;
		cout << "Nsize: " << normalVT.size() << endl;
		cout << "TRIsize: " << triangles.size() << endl;
	}
}

void init(void){
	glShadeModel(GL_SMOOTH);
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
	glEnable(GL_DEPTH_TEST);

	GLfloat lightKa[] = {.2f, .2f, .2f, 1.0f};  // ambient light
    GLfloat lightKd[] = {.7f, .7f, .7f, 1.0f};  // diffuse light
    GLfloat lightKs[] = {1, 1, 1, 1};           // specular light
    glLightfv(GL_LIGHT0, GL_AMBIENT, lightKa);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, lightKd);
    glLightfv(GL_LIGHT0, GL_SPECULAR, lightKs);
	float lightPos[4] = {200, 200, 100, 1};
	glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
	//glEnable(GL_LIGHTING);
	//glEnable(GL_LIGHT0);

	loadFile();
}

// Tracks mouse's motion, and calculate the rotation required by the model: (turnX, turnY)
void motion(int x, int y){
	turnX = turnX + (x - rotateBeginX);
	turnY = turnY + (y - rotateBeginY);
	if(DEBUG)
		cout << "X: " << turnX << "\t Y: "<< turnY << endl;
	rotateBeginX = x;
	rotateBeginY = y;
	glutPostRedisplay();
}

void mouse(int button, int state, int x, int y){
	if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
		rotateBeginX = x;
		rotateBeginY = y;
	}
}

// Move the camera's position along the X-Z plane according to camX and camZ
void zoom(int i){
	camX = camX + i*(lookX)*0.1;
	camZ = camZ + i*(lookZ)*0.1;
	glLoadIdentity();

	glLoadIdentity();
	if(DEBUG)
		cout << "\tcam: [" << camX << " | " << camY << " | " << camZ << "]\t[" << lookX << " | " << lookY << " | " << lookZ << "]" << endl;
	gluLookAt(camX,camY,camZ, camX+lookX,lookY,camZ+lookZ, 0.0f,1.0f,0.0f);
	glutPostRedisplay();
}

// Rotate the camera
void turnCam(float ang) {
	lookX = sin(ang);
	lookZ = -cos(ang);

	glLoadIdentity();
	if(DEBUG)
		cout << "\tcam: [" << camX << " | " << camY << " | " << camZ << "]\t[" << lookX << " | " << lookY << " | " << lookZ << "]" << endl;

	gluLookAt(camX,camY,camZ, camX+lookX,lookY,camZ+lookZ, 0.0f,1.0f,0.0f);
	glutPostRedisplay();
}

// Changes the camera's position and target using zoom() and turnCam()
void inputKey(int key, int x, int y){
	switch(key){
	case GLUT_KEY_UP: 	if(DEBUG) cout<<"^\n"; zoom(5); break;
	case GLUT_KEY_DOWN: 	if(DEBUG) cout<<"v\n"; zoom(-5); break;
	case GLUT_KEY_LEFT: 	if(DEBUG) cout<<"<\n"; angle -= 0.01f; turnCam(angle); break;
	case GLUT_KEY_RIGHT: 	if(DEBUG) cout<<">\n"; angle += 0.01f; turnCam(angle); break;
	}
}

// Reset view and exit
void keyboard(unsigned char key, int x, int y){
	switch (key) {
	case 'r':
	case 'R': 
		angle = 0;
		lookX = lookY = 0.0f; lookZ = -1.0f;
		turnX = turnY = 0;
		camX = 0.0f; camY = 0.0f; camZ = 0.0f;
		glLoadIdentity();
		gluLookAt(camX,camY,camZ, camX+lookX,lookY,camZ+lookZ, 0.0f,1.0f,0.0f);
		glutPostRedisplay();
		break;
	case 27: exit(0); break;
	default: break;
    }
}

void reshape(int w, int h){
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(40.0, (GLfloat) w / (GLfloat) h, 1.0, 500.0);
	
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(camX,camY,camZ, camX+lookX,lookY,camZ+lookZ, 0.0f,1.0f,0.0f);
}


// Aquires indies of vectices and normals, and render the model using glDrawElements()
void drawBuilding(){
	//GLuint texture;

	GLfloat vertexIndex0[3];			// index of vector
	GLfloat vertexIndex1[3];
	GLfloat vertexIndex2[3];
	GLfloat normalIndex[3];				// index of normal
	unsigned int textureIndex;			// index of texture
	GLfloat textureCoordinate0[2];		// index of texture corrdinate
	GLfloat textureCoordinate1[2];
	GLfloat textureCoordinate2[2];

	glEnable(GL_TEXTURE_2D);
	glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
	
	for(int renderTriangle=0; renderTriangle<triangles.size(); renderTriangle++){

		for(int i=0; i<3; i++){
			vertexIndex0[i] = vertexVT[triangles[renderTriangle].vertex[0]*3+i];
			vertexIndex1[i] = vertexVT[triangles[renderTriangle].vertex[1]*3+i];
			vertexIndex2[i] = vertexVT[triangles[renderTriangle].vertex[2]*3+i];
			normalIndex[i] = normalVT[triangles[renderTriangle].normal+i];
		}
		textureIndex = triangles[renderTriangle].texture;
		for(int i=0; i<2; i++){
			textureCoordinate0[i] = triangles[renderTriangle].textureCoordinate[0+i];
			textureCoordinate1[i] = triangles[renderTriangle].textureCoordinate[2+i];
			textureCoordinate2[i] = triangles[renderTriangle].textureCoordinate[4+i];
		}
		
		glBindTexture(GL_TEXTURE_2D, texturePPM[textureIndex]);
		//cout << "texturePPM[" << textureIndex << "]: " << texturePPM[textureIndex] << endl;

		glPushMatrix();
		
		glEnable(GL_CULL_FACE);
		glBegin(GL_TRIANGLES);
			glNormal3fv(normalIndex);
			glTexCoord2fv(textureCoordinate0); glVertex3fv(vertexIndex0);
			glTexCoord2fv(textureCoordinate1); glVertex3fv(vertexIndex1);
			glTexCoord2fv(textureCoordinate2); glVertex3fv(vertexIndex2);
		glEnd();
		glEnable(GL_CULL_FACE);
		glPopMatrix();

		//glDeleteTextures(1, &texturePPM[textureIndex]);
	}
	glDisable(GL_TEXTURE_2D);
}

void drawBox(){
	GLuint myTexture;
	int textureWidth, textureHeight; 

	unsigned char* tr;
	try{
		tr = PPM::Read("t4.ppm", textureWidth, textureHeight);
	}catch (string str){
		cout << "Exception from PPM.cpp: " << str << endl;
	}
	glGenTextures(1, &myTexture);
	glBindTexture(GL_TEXTURE_2D, myTexture);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, textureWidth, textureHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, tr);

	delete tr;

	glEnable(GL_CULL_FACE);
	glFrontFace(GL_CCW);
	glEnable(GL_TEXTURE_2D);
	//glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
	//glBindTexture(GL_TEXTURE_2D, myTexture);

	glBindTexture( GL_TEXTURE_2D, myTexture );
	glBegin(GL_TRIANGLES);
		glTexCoord2f(0.0, 0.0);		glVertex3f(0, 0, 0);
		glTexCoord2f(0.0, 1.0);		glVertex3f(1, 0, 0);
		glTexCoord2f(1.0, 0.0);		glVertex3f(0, 1, 0);
	glEnd();

	//glBegin(GL_TRIANGLES);
	//	glTexCoord2f(0.0, 0.0);		glVertex3f(1,-1,1);
	//	glTexCoord2f(1.0, 1.0);		glVertex3f(1,-1,-1);
	//	glTexCoord2f(1.0, 0.0);		glVertex3f(0,0,0);
	//glEnd();

	////glBindTexture( GL_TEXTURE_2D, myTexture );
	//glBegin(GL_TRIANGLES);
	//	glTexCoord2f(0.0, 0.0);		glVertex3f(-1,-1,1);
	//	glTexCoord2f(1.0, 1.0);		glVertex3f(1,-1,1);
	//	glTexCoord2f(1.0, 0.0);		glVertex3f(0,0,0);
	//glEnd();

	//glBegin(GL_TRIANGLES);
	//	glTexCoord2f(0.0, 0.0);		glVertex3f(1,-1,-1);
	//	glTexCoord2f(1.0, 1.0);		glVertex3f(-1,-1,-1);
	//	glTexCoord2f(1.0, 0.0);		glVertex3f(0,0,0);
	//glEnd();
	//glBegin(GL_TRIANGLES);
	//	glTexCoord2f(0.0, 1.0);		glVertex3f(-1,-1,-1);
	//	glTexCoord2f(0.5, 0.5);		glVertex3f(-1,-1,1);
	//	glTexCoord2f(1.0, 1.0);		glVertex3f(0,0,0);
	//glEnd();

	glDisable(GL_TEXTURE_2D);
	glDisable(GL_CULL_FACE);
}

// Displays my name and name of the model
void displayName(){
    glPushMatrix();
    glLoadIdentity();

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluOrtho2D(0, 400, 0, 300);

	void* font = GLUT_BITMAP_9_BY_15;
    float color[4] = {1, 1, 1, 1};
	glRasterPos2f(10, 280);
	string s = modelName;
	for (int i=0; i<s.size(); i++) {
		glutBitmapCharacter(font, s[i]);
	}
	glRasterPos2f(280, 10);
	s = "#29144052";
	for (int i=0; i<s.size(); i++) {
		glutBitmapCharacter(font, s[i]);
	}
	glRasterPos2f(280, 25);
	s = "Wei-Ting Liu";
	for (int i=0; i<s.size(); i++) {
		glutBitmapCharacter(font, s[i]);
	}
    glPopMatrix();

    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
}

void display(void){
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glPushMatrix();

	//glLoadIdentity();
	glTranslatef(0, 0, -100);
	glRotatef(turnY, 1.0, 0.0, 0.0);
	glRotatef(turnX, 0.0, 1.0, 0.0); 

	///////////////////////////////
	//glTranslatef(-22.7, 0, 21.9);
	//glRotatef(-90, 1.0, 0.0, 0.0);
	///////////////////////////////

	//glutSolidTeapot(1.0);
	//drawBox();
	drawBuilding();

	displayName();

	glPopMatrix();
	glutSwapBuffers();
}

int main(int argc, char **argv){
	if(argc != 2){
		cout << "Usage: renderModel.exe [Model File Name]\nex. renderModel.exe BuchananTower.model" << endl;
		system("PAUSE");
		return 0;
	}else{
		modelName = argv[1];
		modelPath = modelName;
	}

	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_DEPTH);
	glutCreateWindow("EECE478 A1 Model Rendering");

	glutReshapeFunc(reshape);
	glutDisplayFunc(display);

	glutKeyboardFunc (keyboard);
	glutSpecialFunc(inputKey);
	glutMouseFunc(mouse);
	glutMotionFunc(motion);

	init();
	glutMainLoop();
}